package comm;

import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Vector;

import comm.IONode.EInitState;
import comm.IONode.EReplyNeed;

// main loop -主循环线程
class MainLoop extends Thread {
	private boolean isEnd;
	private IONodeList nodeList; // 全部IO节点索引

	private LogOutputManager logFileManager = new LogOutputManager("MainLoop");
	private Log log = new Log();

	private final long updatesCheckMinitesInterval; // 循环检测同步需求间隔时间，单位：minite
	private final int accountRemindingCheckHour; // 账户通知检查时间（每天的时间点，单位：小时）
	private final long serveresStateCheckMillisInterval = 10 * 1000; // 通信服务状态（各个线程状态）检测时间间隔，单位：mS
	private final long bookingsStateUpdateCheckMillisInterval = 2 * 60 * 1000; // 更新预约状态查询时间颗粒度设置，每过此时间间隔检查当前时间是否符合执行条件
	private final long updatesInstrumentStateListMinitesInterval = 1; // 单位：分钟


	// 示例：每小时第1分钟和第31分钟执行一次，1分钟之内至多执行一次，该值可设为2-29（数值越小，检查时间点频率越高，第一次执行时效性越好），超过该超时时间后继续等待合适时间点执行更新

	MainLoop() {
		this.setName(this.getClass().getName()); // ThreadName
		this.nodeList = new IONodeList();// 创建io节点索引
		int num = PropertyManager.getInt("UpdatesCheckIntervalMinutes", 30);
		if (num < 0) {
			num = 30;
		}
		this.updatesCheckMinitesInterval = num;
		num = PropertyManager.getInt("AccountRemindingCheckHour", 0);
		if (num < 0 || num > 23) {
			num = 0;
		}
		this.accountRemindingCheckHour = num;
		this.isEnd = false;
	}

	boolean isEnd() {
		return this.isEnd;
	}

	void setEnd() {
		this.isEnd = true;
	}

	// 主循环线程
	// // 异常终止时，打印异常，关闭所有仍在运行到线程，释放资源
	public void run() {
		try {
			log.print(Log.info, "MainLoopThread Start");

			// 初始化
			ListenThread listenThread = new ListenThread(this.nodeList.getDoingForListenThread()); // 刷卡机节点监听线程
			final Notification notification = new Notification(); // 通知接口
			final DBAccess iDBAccess = new DBAccess(this.log); // 数据库接口

			final WebServiceRun serverForWeb = new WebServiceRun(); // WebService控制
			ServerForWitness serverForWitness = new ServerForWitness(); // 监视服务消息处理线程

			final TimeOutTimer updatesCheckStateTimer = new TimeOutTimer(this.updatesCheckMinitesInterval * 60 * 1000); // 全库待同步扫描定时器(mS)
			final TimeOutTimer serveresStateCheckTimer = new TimeOutTimer(this.serveresStateCheckMillisInterval); // 通信服务状态检查定时器(mS)
			final TimeOutTimer bookingsStateUpdateCheckTimer = new TimeOutTimer(this.bookingsStateUpdateCheckMillisInterval); // 预约表周期性更新定时器(mS)（针对未同步的过期预约状态变更，一般为未审批的预约过期）
			final TimeOutTimer updatesInstrumentStateListTimer = new TimeOutTimer(this.updatesInstrumentStateListMinitesInterval * 60 * 1000); // 仪器状态列表更新定时器(mS)（用于检测仪器离线时间过长）
			
			// 打开数据库连接
			// // 数据库连接不上时会阻塞几分钟
			if (false == iDBAccess.openDbConnection()) {
				this.setEnd();
			}

			// 打开WebService
			if (false == serverForWeb.start(this.nodeList.getDoingForWeb())) {
				this.setEnd();
			}

			// 启动监视服务监听
			try {
				serverForWitness.start();
			} catch (IllegalThreadStateException e) {
				Log.println(e.toString());
				this.setEnd();
			}

			// 启动刷卡机节点监听
			try {
				listenThread.start();
			} catch (IllegalThreadStateException e) {
				Log.println(e.toString());
				this.setEnd();
			}

			// 主循环
			long loopInterval = 0;// ms
			long lastTimeMills = System.currentTimeMillis();
			long curTimeMills = System.currentTimeMillis();
			try {
				while (false == this.isEnd) {
					// debug
					// log.print(Log.info, "new loop");
					curTimeMills = System.currentTimeMillis() - lastTimeMills;
					// Log.println("当前两次循环时间间隔：" + curTimeMills + "maxTime" +
					// loopInterval);
					if (curTimeMills > loopInterval) {
						loopInterval = curTimeMills;
						// Log.println("max loop time 间隔刷新：" + curTimeMills);
					}
					lastTimeMills = System.currentTimeMillis();

					// sleep
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						Log.println(e.toString());
					}

					// 检查各个服务状态
					// // 包括监听线程，监视服务监听线程，数据库连接，若某服务有问题则终止通信服务，并释放资源
					// // ?？是否需要，怎样检测webservice状态（暂未检测）
					if (true == serveresStateCheckTimer.getTimeOutState()) {
						// debug
						// if(this.nodeList.getMaxInitedNodeIndex() > 50){
						// serverForWitness.end();
						// Thread.sleep(100);
						// }

						// 客户端节点监听线程状态
						if (true == listenThread.isTimeOut() || true == listenThread.isEnd()) {
							log.print(Log.warn, "客户端节点监听线程故障，重启监听线程。info:" + " listenThread.isTimeOut():" + listenThread.isTimeOut() + " listenThread.isEnd():" + listenThread.isEnd());
							listenThread.end();
							logFileManager.printToFile(listenThread.getLog()); // 刷卡机节点监听线程日志写入文件
							listenThread = null;
							listenThread = new ListenThread(this.nodeList.getDoingForListenThread());
							listenThread.start();
						}

						// 监视服务监听线程
						if (true == serverForWitness.isEnd()) {
							log.print(Log.warn, "监视服务监听线程故障，重启监听线程。info:" + " serverForWitness.isEnd():" + serverForWitness.isEnd());
							serverForWitness.end();
							logFileManager.printToFile(serverForWitness.getLog()); // 监视服务监听线程日志写入文件
							serverForWitness = null;
							serverForWitness = new ServerForWitness();
							serverForWitness.start();
						}

						// 数据库连接
						if (false == iDBAccess.getConnectState()) {
							// 需要终止通信服务
							log.print(Log.error, "数据库连接故障，终止通信服务。info：" + "iDBAccess.getConnectState():" + iDBAccess.getConnectState());
							this.setEnd();
							break;
						}

						// 重置查询定时器
						serveresStateCheckTimer.resetTimer();
					}

					// 轮循所有io节点（刷卡机节点）
					for (int i = 0; i < nodeList.MAXNUM_IONODE; i++) {
						// 处理待初始化节点
						nodeList.processOneUninitNode(this.log, notification, iDBAccess, i);
					}
					for (int i = 0; i < nodeList.MAXNUM_IONODE; i++) {
						// 处理已初始化节点
						nodeList.processOneInitedNode(this.log, notification, iDBAccess, i);
					}

					// 处理监视服务消息（有时间间隔限制），暂时仅考虑简单回复
					// 监视服务每隔一段时间发条询问消息，通信服务执行到此给出一个回复，监视服务在允许时间内收到回复即认为通信服务正常
					// 监视服务可重发询问消息，在有限次数内收到回复即可。通信服务的监视服务监听线程仅记录一下是否需回复，不做缓存
					this.nodeList.processWitnessData(serverForWitness);

					// 主动扫描数据库看是否有更新需求
					// // 有间隔时间限制，概率要小（例如半小时查一次）
					// // 附带触发用户账户通知（每天一次每个小时查一次，没有必要精确到分钟）
					// // 暂时考虑，主动扫描数据库时间0-60分钟，账户扫描为每天一次
					if (true == updatesCheckStateTimer.getTimeOutState()) {
						this.nodeList.doUpdatesCheck(iDBAccess); // 待同步检查

						// 每天执行一次的工作
						Calendar now = Calendar.getInstance();
						if (now.get(Calendar.HOUR_OF_DAY) == this.accountRemindingCheckHour //
								&& now.get(Calendar.MINUTE) >= 0 //
								&& now.get(Calendar.MINUTE) < 0 + this.updatesCheckMinitesInterval //
						) {
							// 账户通知检查
							this.nodeList.doAccountCheck(iDBAccess);
							
							// 更新当天惩罚期进出，并发送前一天进出惩罚期邮件通知
							this.nodeList.doPunishCheck(iDBAccess);
							
							// 无刷卡机仪器预约的状态更新（每天更新一次，将到期的预约标记为已完成）
							this.nodeList.doUpdateNoCardBookingsState(iDBAccess);
						}
						updatesCheckStateTimer.resetTimer();
					}

					// 周期更新预约处理过期状态
					// // 管理员未处理的预约过期时由通信服务每到预约最短时间块结束时查询数据库，若有过期预约则更新状态
					if (true == bookingsStateUpdateCheckTimer.getTimeOutState()) {
						// 超时有效
						Calendar now = Calendar.getInstance();
						// 定时执行（每小时第1分钟和31分钟执行一次）
						if (now.get(Calendar.MINUTE) % 30 >= 1 //
								&& now.get(Calendar.MINUTE) % 30 < 2 //
						) {
							// 时间点有效
							this.nodeList.doUpdateBookingsState(iDBAccess);
							bookingsStateUpdateCheckTimer.resetTimer(); // 处理有效后才重置超时定时器
						}
					}
					
					// 刷新仪器状态到仪器状态列表，并处理仪器离线时间超长提醒
					if(true == updatesInstrumentStateListTimer.getTimeOutState()) {
						this.nodeList.doNodesStateCheck(iDBAccess);
						updatesInstrumentStateListTimer.resetTimer();
					}

					// 日志处理
					logFileManager.printToFile(this.log); // 处理主循环线程日志（写入文件）
					// // 处理其他日志（刷卡机节点监听，监视服务监听，数据库日志）
					logFileManager.printToFile(listenThread.getLog()); // 刷卡机节点监听线程日志写入文件
					logFileManager.printToFile(serverForWitness.getLog()); // 监视服务监听线程日志写入文件
					logFileManager.printToFile(iDBAccess.getLog()); // 数据库日志写入文件

				}
			} catch (Exception e) {
				e.printStackTrace();
				Log.println(e);
				log.print(Log.error, e.toString());
				this.setEnd();
			}

			// 关闭所有已开服务，释放资源

			// 关闭监听线程
			listenThread.end();

			// 关所有IO节点
			nodeList.delAllNode(this.log);

			// 关监视服务监听
			serverForWitness.end();

			// 关WebService
			serverForWeb.stop();

			// 关数据库
			iDBAccess.closeDbConnection();

			// 其他服务日志写入文件
			Thread.sleep(100); // 等一小会儿等待各个线程退出
			logFileManager.printToFile(listenThread.getLog()); // 刷卡机节点监听线程日志写入文件
			logFileManager.printToFile(serverForWitness.getLog()); // 监视服务监听线程日志写入文件

		} catch (Exception e) {
			Log.println(e);
			log.print(Log.error, e.toString());
		} finally {
			// 通知管理员通信服务终止
			NoteList newNoteList = new NoteList();
			newNoteList.add(NoteServerSys.getNote() //
					//.setDetail("CommServerEnd") //
					);
			newNoteList.add(NoteSMSServerSys.getNote() //
					//.setDetail("") // 
					);
			newNoteList.sendAll();

			log.print(Log.info, "MainLoopThread Stop");
			logFileManager.printToFile(this.log);
		}

	}

}

// IONodeList -io节点索引
class IONodeList {
	final int MAXNUM_IONODE; // 调试完成后改为一个比较大的值如1024（通过配置文件设置，最大65536）
	final static long TimeoutTimerMaxTime = 500; // 关闭节点时间上限（mS），若超时则强制关闭
	final static int MAXNUM_NODEDATA = 15; // 单个刷卡机节点单次处理消息条数上限（应该稍大于IO接收缓存的上限）
	private IONodesVector uninitNode; // 一般情况下，只用到前几个，大小设大不会影响效率
	private IONodesVector initedNode; // 每个仪器ID对应节点序号确定（节点序号为刷卡及ID对65536余数）
	private NodeStateVector nodeStateList; // 已初始化刷卡机的状态列表，用于检查刷卡机离线时长（提醒管理员仪器离线时间过长）
	private LogOutputManager logFileManagerforIO = new LogOutputManager("IONodes"); // 写IO日志(??日志中不同IO区分)
	private int maxNodeNum = 0; // 最大已出现已初始化刷卡机节点序号
	
	private final int MaxInstrumentOfflineMinites; // 仪器超长离线时间，单位：分钟（若离线超过10分钟，需发送提醒）
	private final int MaxOfflineFaultMinites; // 仪器离线故障检测时间阈值，单位：分钟（若离线超过5分钟仪器数目>3，则进入离线故障模式）
	private final int MaxOfflineFaultNum; // 仪器离线故障检测数量阈值，单位：个数（若离线超过5分钟仪器数目>3，则进入离线故障模式）
	
	private boolean isOfflineFaultMode = false; // 是否处于离线故障模式（短时间内出现过多离线）

	IONodeList() {
		// 最大仪器个数
		int num = PropertyManager.getInt("MaxInstrumentCount", 10000);
		if (num <= 0 || num >= 65536) {
			Log.println("配置文件数据非法，MaxInstrumentCount:" + num);
			num = 10000;
		}
		this.MAXNUM_IONODE = num;
		// 仪器离线超长提醒时间
		num = PropertyManager.getInt("MaxInstrumentOfflineMinites", 10);
		if (num <= 0 || num >= 65536) {
			Log.println("配置文件数据非法，MaxInstrumentOfflineMinites:" + num);
			num = 10;
		}
		this.MaxInstrumentOfflineMinites = num;
		// 仪器离线故障检测时间阈值
		num = PropertyManager.getInt("MaxOfflineFaultMinites", 10);
		if (num <= 0 || num >= 65536) {
			Log.println("配置文件数据非法，MaxOfflineFaultMinites:" + num);
			num = 5;
		}
		this.MaxOfflineFaultMinites = num;
		// 仪器离线故障检测数量阈值
		num = PropertyManager.getInt("MaxOfflineFaultNum", 10);
		if (num <= 0 || num >= 65536) {
			Log.println("配置文件数据非法，MaxOfflineFaultNum:" + num);
			num = 3;
		}
		this.MaxOfflineFaultNum = num;
		this.uninitNode = new IONodesVector(this.MAXNUM_IONODE);
		this.initedNode = new IONodesVector(this.MAXNUM_IONODE);
		this.nodeStateList = new NodeStateVector(this.MAXNUM_IONODE);
	}

	// 检查数据库看是否有同步需求
	// // 调执行模块执行全库扫描，调用频率要尽可能小
	// // 由执行模块根据扫描结果修改各个IO节点同步状态
	// // 附带触发用户账户通知（每天一次）
	void doUpdatesCheck(final DBAccess aDBAccess) {
		Log.println("通信服务主动查数据库，读取同步需求", Log.defaultLogFileNameTemp, false);
		IOUpdateNeedList listOfIOUpdateNeed = new IOUpdateNeedList();
		aDBAccess.checkNewUpdates(listOfIOUpdateNeed);
		listOfIOUpdateNeed = null;
	}

	// 账户检查，并发布提醒
	void doAccountCheck(final DBAccess aDBAccess) {
		Log.println("账户检查", Log.defaultLogFileNameTemp, false);
		NoteList newNoteList = new NoteList();
		if (true == aDBAccess.checkAccount(newNoteList)) { // 账户余额不足等提醒
			newNoteList.sendAll();
		}

		aDBAccess.checkCharge(); // 大客户检查
	}

	// 更新当天惩罚期进出，并发送前一天进出惩罚期邮件通知
	void doPunishCheck(final DBAccess aDBAccess) {
		aDBAccess.updatePunishEndList(); // 刷新当天出惩罚期列表
		// 发送前一天进出惩罚期邮件通知
		NoteList newNoteList = new NoteList();
		if(true == aDBAccess.checkPunishNote(newNoteList)) {
			newNoteList.sendAll();
		}
	}
	
	// 刷新仪器状态到仪器状态列表，并检测是否有仪器离线时间超长
	// //刷卡机由在线状态变为离线状态超过一定时长即发短信通知仪器管理员
	//  // 若同时有多台仪器离线则认为出现离线故障，故障期间发短信给系统管理员（不再通知仪器管理员）
	// 20140511更新系统管理员通知
	// 在线状态（非0表示在线，0表示离线）
	void doNodesStateCheck(final DBAccess aDBAccess) {
		// 刷新仪器状态
		NoteList newNoteList = new NoteList(); // 通知消息列表
		int NumofOnline = 0; // 当前在线个数
		int NumofOfflineAdd = 0; // 新增离线个数（本次检查中由非离线转为离线）
		int NumofOfflineFaultCheck = 0; // 达到离线故障时间阈值且未发送离线故障通知仪器数目
		boolean isOfflineFaultEnd = false; // 故障模式终止检测
		final long rightnow = System.currentTimeMillis();
		// 浏览仪器状态变更
		for(int i = 0; i <= this.maxNodeNum; ++i) {
			// 获取仪器当前状态
			int newState = 0; // 默认离线
			if(false == isIONodeNull(i, IONodeType.INITED)){
				// 若结点非空，则读取当前状态
				newState = this.initedNode.getIONode(i).getLastState();
			}
			// 更新当前在线仪器个数
			if(0 != newState) {				
				NumofOnline++;
			}
			// 仪器状态改变，需更新状态
			if(newState != nodeStateList.vector.get(i).state) {
				// 状态改变时需要更新
				if(0 == newState) {
					// 需要通知（在线变离线）
					nodeStateList.vector.get(i).needNotice = true;
					// 更新新增离线数目
					NumofOfflineAdd++;
				} else {
					// 其他状态改变不需要通知（包括离线变在线，或在线的多种状态之间转换）
					nodeStateList.vector.get(i).needNotice = false;
					// 若故障模式已通知仪器由离线转为在线则故障模式终止（排除非本次离线故障期间转为离线或并未通知故障情况）
					if( (true == isOfflineFaultMode) && (0 == nodeStateList.vector.get(i).state) && (true == nodeStateList.vector.get(i).isNoticeOfflineFault)) {
						isOfflineFaultEnd = true;
					}
				}
				nodeStateList.vector.get(i).state = newState; // 新状态
				nodeStateList.vector.get(i).timeMills = rightnow;	// 最近一次状态更新时间
			}
			// 检查并更新达到离线故障时间阈值且未发送离线故障通知仪器数目
			if ( (true == nodeStateList.vector.get(i).needNotice) && (rightnow - nodeStateList.vector.get(i).timeMills > MaxOfflineFaultMinites * 60 * 1000) && (false == nodeStateList.vector.get(i).isNoticeOfflineFault)) {
				NumofOfflineFaultCheck++;
			}
				
		}
		// 检查待通知并通知
		if(false == isOfflineFaultMode) {
			// 正常模式
			if(NumofOfflineFaultCheck > MaxOfflineFaultNum) {
				// 进入离线故障模式
				Log.println("仪器状态刷新：由正常模式转为离线故障模式，info: 新增离线个数" + NumofOfflineAdd + "达到离线故障时间阈值个数" + NumofOfflineFaultCheck + "当前在线个数" + NumofOnline);
				isOfflineFaultMode = true;
				// 通知系统管理员（当前所有离线仪器）
				aDBAccess.getNoteOfflineTooMany(newNoteList, NumofOfflineAdd, NumofOnline);
				// 标记故障已通知
				for(int i = 0; i <= this.maxNodeNum; ++i) {
					if (true == nodeStateList.vector.get(i).needNotice) {
						// 标记故障已通知
						nodeStateList.vector.get(i).isNoticeOfflineFault = true;
					}
				}
			} else {
				// 若有少数仪器离线超时则通知仪器管理员
				for(int i = 0; i <= this.maxNodeNum; ++i) {
					// 检查并整理通知
					if ( (true == nodeStateList.vector.get(i).needNotice) && (rightnow - nodeStateList.vector.get(i).timeMills > MaxInstrumentOfflineMinites * 60 * 1000)) {
						// 离线超过上限（默认10分钟）
						// 通知仪器管理员
						final int instrumentID = i%65536 + 65536;
						aDBAccess.getNoteInstrumentOffline(instrumentID, newNoteList);
						// 清标志位
						nodeStateList.vector.get(i).needNotice = false;
					}
				}
			}
		} else {
			// 离线故障模式
			if(true == isOfflineFaultEnd) {
				// 退出离线故障模式
				Log.println("仪器状态刷新：由离线故障模式转为正常模式，info: 新增离线个数" + NumofOfflineAdd + "达到离线故障时间阈值个数" + NumofOfflineFaultCheck + "当前在线个数" + NumofOnline);
				isOfflineFaultMode = false;
				// 清除离线故障已通知标记及待通知标志
				// 本次检查到的离线仪器不予通知（认为管理员已开始处理故障，转为正常状态后，本次检查及以往检查中待通知将无效，管理员使仪器上线后仪器重新离线将触发下一次离线检测）
				//（本次离线故障期间离线仪器若未上线则不在下次离线检测范围内，不会重复触发离线检测，需系统管理员处理故障后保证仪器上线）
				for(int i = 0; i <= this.maxNodeNum; ++i) {
					nodeStateList.vector.get(i).needNotice = false;
					nodeStateList.vector.get(i).isNoticeOfflineFault = false;
				}
			} else {
				if(NumofOfflineFaultCheck > 0) {
					// 离线模式下有仪器达到离线故障时间阈值
					// 若有仪器短时离线且离线期间没有其他仪器离线达到阈值则不在通知范围内
					Log.println("仪器状态刷新：离线模式下有仪器达到离线故障时间阈值，info: 新增离线个数" + NumofOfflineAdd + "达到离线故障时间阈值个数" + NumofOfflineFaultCheck + "当前在线个数" + NumofOnline);
					// 通知系统管理员（当前所有离线仪器）
					aDBAccess.getNoteOfflineTooMany( newNoteList, NumofOfflineAdd, NumofOnline);
					// 标记故障已通知
					for(int i = 0; i <= this.maxNodeNum; ++i) {
						if (true == nodeStateList.vector.get(i).needNotice) {
							// 标记故障已通知
							nodeStateList.vector.get(i).isNoticeOfflineFault = true;
						}
					}
				}
			}
			
		}
		// 发送通知
		if(newNoteList.size() > 0) {
			newNoteList.sendAll();
		}
	}
		
	// 周期性更新预约状态（针对未审批过期预约）
	void doUpdateBookingsState(final DBAccess aDBAccess) {
		Log.println("更新预约状态-针对未审批过期预约", Log.defaultLogFileNameTemp, false);
		aDBAccess.updateBookingsState();
	}
	
	// 周期性更新预约状态（无刷卡机仪器的已完成预约）
	void doUpdateNoCardBookingsState(final DBAccess aDBAccess) {
		Log.println("更新预约状态-针对无刷卡机仪器的已完成预约", Log.defaultLogFileNameTemp, false);
		aDBAccess.updateNoCardBookingsState();
	}

	// 处理单个未初始化节点数据
	// ？？处理不能阻塞（处理一个节点的最长时间）// 先处理完当前已存在的数据
	// 每次处理一个IO节点：看是否线程已超时终止，读接收池，处理并发送回复消息
	void processOneUninitNode(final Log aLog, final Notification aNotification, final DBAccess aDBAccess, final int aNodeNum) {
		// 参数检查
		if (null == aNotification || null == aDBAccess || true == this.isIONodeNull(aNodeNum, IONodeType.UNINIT)) {
			// 参数有误
			return;
		} else if (true == this.isIONodeThreadEnd(aNodeNum, IONodeType.UNINIT)) {
			// 判断节点是否已终止（或已被终止）
			aLog.print(Log.info, "未初始化节点线程已终止，删除节点" + aNodeNum);
			logFileManagerforIO.printToFile(getNodeLog(aNodeNum, IONodeType.UNINIT)); // IO节点日志写入文件
			// 删除节点前的善后工作
			// // 标记预约已同步，并通知预约同步失败(未初始化节点似乎无此操作必要)
			//this.processBookingListUpdateFail(aNotification, aDBAccess, aNodeNum, IONodeType.UNINIT);
			// 删除节点
			this.delNode(aLog, aNodeNum, IONodeType.UNINIT); // 强制删除节点（有超时时限的阻塞操作）
			return;
		}

		// 节点正常，开始处理节点消息，处理节点消息接收队列并发送回复
		logFileManagerforIO.printToFile(this.getNodeLog(aNodeNum, IONodeType.UNINIT)); // 日志处理
		// 取当前接收池大小，若处理时间大于刷卡机端超时时间可能出现多条重复请求
		int dataNodeNum = this.getRecvDataCount(aNodeNum, IONodeType.UNINIT);
		if (dataNodeNum > IONodeList.MAXNUM_NODEDATA) {
			aLog.print(Log.warn, "未初始化节点待处理消息过多，序号：" + aNodeNum + "消息条数：" + dataNodeNum);
			// 单次处理消息条数上限
			dataNodeNum = IONodeList.MAXNUM_NODEDATA;
		}
		DataRecv recvDataNode = this.recvDataNode(aNodeNum, IONodeType.UNINIT); // 取一条接收消息
		DataSend sendDataNode = null; // 接收消息触发的回复消息
		while (recvDataNode != null && (dataNodeNum--) > 0) {
			// 逻辑处理--未初始化状态
			final int dataID = recvDataNode.getDataID();
			Log.println("待处理消息dataID " + dataID + ":" + recvDataNode.toString());
			if (DataHeartBeatRecv.dataID == dataID) {
				// 收到心跳包
				// 检查消息内容无误则将节点转移至已初始化队列
				// // 检查心跳包格式
				final int instruementID = ((DataHeartBeatRecv) recvDataNode).getMsgInstrumentID();
				// 获取mac地址
				String[] macAddres = new String[1];
				aDBAccess.getMacAddr(instruementID, macAddres);
				final String macAddr = macAddres[0];// 查到到mac地址可能为空或"00:00:00:00:00:00"（未初始化）

				if (IONode.instrumentIDToNodeNum(instruementID) >= 0 && IONode.instrumentIDToNodeNum(instruementID) <= this.MAXNUM_IONODE && null != macAddr) {
					// 仪器ID有效，且可以从数据库查出仪器ID对应到仪器MAC地址
					// 仪器ID有效性判断，判断方法取MAXNUM_IONODE可能不够严格？？
					if (true == macAddr.equals("00:00:00:00:00:00")) {
						Log.println("刷卡机未初始化:仪器ID" + instruementID);
						this.endIONodeThread(aNodeNum, IONodeType.UNINIT);
						break;
					} else {
						Log.println("主循环线程收到合法心跳包-第一次:仪器ID" + instruementID + "仪器MAC地址" + macAddr);
						this.uninitNode.getIONode(aNodeNum).setInstrumentID(instruementID);
						this.uninitNode.getIONode(aNodeNum).setMacAddr(macAddr);
						// 发送心跳包回复（利用未初始化节点身份发送回复）
						sendDataNode = DataHeartBeatSend.getInstance(((DataHeartBeatRecv) recvDataNode).getClientTimeStamp());
						if (null != sendDataNode) {
							if (false == sendDataNode(aNodeNum, IONodeType.UNINIT, sendDataNode, EReplyNeed.NOREPLY)) {
								aLog.print(Log.warn, "发送回复消息失败" + " NodeNum:" + aNodeNum);
								break;
							}
						}
						sendDataNode = null;
						// 注：不需要清空收发队列（初始化完成后刷卡机可能立即开始主动发送初始化后消息）
						this.addInitedNode(aLog, aNodeNum, instruementID);
						break;
					}
				} else {
					Log.println("第一个心跳包仪器ID或仪器ID查不出mac地址（有可能是数据库问题）。instruementID：" + instruementID + "macAddr:" + macAddr);
					this.endIONodeThread(aNodeNum, IONodeType.UNINIT);
					break;
				}
			} else if (DataCommandRecv.dataID == dataID) {
				// 收到通用消息
				DataCommandRecv dataRecvCommand = (DataCommandRecv) recvDataNode;
				if (true == dataRecvCommand.isInstrumentListReq()) {
					// 若为仪器列表请求，则返回仪器列表
					sendDataNode = DataInstrumentList.getInstance(aDBAccess);
				} else if (true == dataRecvCommand.isReplyOK() && EInitState.WAITING_REPLY == this.uninitNode.getIONode(aNodeNum).getInitState()) {
					// 收到初始化列表发送完毕的回复消息
					// // 必须处在Initing状态才可能收到OK回复消息
					if (true == dataRecvCommand.isReplyInitedOK()) {
						aLog.print(Log.info, "IO节点初始化列表发送完成，仪器ID：" + this.getInstrumentID(aNodeNum, IONodeType.UNINIT));
						// **若收到初始化完成确认则关闭连接（由刷卡机主动关闭连接）
						// aLog.print(Log.info, "IO节点初始化完成，关闭节点，仪器ID：" +
						// this.getInstrumentID(aNodeNum, IONodeType.UNINIT));
						// this.endIONodeThread(aNodeNum, IONodeType.UNINIT); //
						// 软关闭IO节点线程（此处为异步关闭）
						// break;
					} else {// 若为普通需回复消息的回复则直接写数据库同步成功
						dataRecvCommand.getDataSendOfReply().setUpdated(aDBAccess);
					}
					sendDataNode = null;
				} else if (true == dataRecvCommand.isReplyERR()) {
					Log.println("主循环线程收到失败确认消息" + " NodeNum:" + aNodeNum);
					sendDataNode = null;
				}
				dataRecvCommand = null;

			} else if (DataInstrumentBinding.dataID == dataID) {
				// 收到仪器ID关联请求InstrumentBinding（绑定或解绑定）
				// 写数据库成功则返回OK消息，然后发送初始化消息（列表），并插入初始化完成通知
				// 解绑定操作则直接写数据库并返回状态
				final DataInstrumentBinding dataInstrumentBinding = (DataInstrumentBinding) recvDataNode;
				Log.println("主循环线程收到：InstrumentBinding-BindingState:" + dataInstrumentBinding.getBindingState());
				final int instruementID = dataInstrumentBinding.getInstrumentId(); // 仪器ID
				final String macAddr = dataInstrumentBinding.getClientMacAddr(); // 仪器Mac地址
				Log.println("InstrumentBinding 仪器ID" + instruementID + "仪器MAC地址" + macAddr);
				sendDataNode = dataInstrumentBinding.getReplyData(aDBAccess); // 可能成功或失败
				if (DataInstrumentBinding.EBindingState.Request == dataInstrumentBinding.getBindingState() && true == DataCommandSend.isCommandReplyOK(sendDataNode)) {
					// 请求绑定
					// 当前消息为请求绑定消息且同步成功，则开始初始化（发送初始化列表）
					this.uninitNode.getIONode(aNodeNum).setInstrumentID(instruementID);
					this.uninitNode.getIONode(aNodeNum).setMacAddr(macAddr);
					this.uninitNode.getIONode(aNodeNum).setInitState(EInitState.TO_SEND_INIT_LIST); // 跳到init状态，同时推出该节点消息的处理
				} else if (DataInstrumentBinding.EBindingState.Confirm == dataInstrumentBinding.getBindingState() && true == DataCommandSend.isCommandReplyOK(sendDataNode)) {
					// 当前消息为确认绑定消息且同步成功

				} else if (DataInstrumentBinding.EBindingState.Release == dataInstrumentBinding.getBindingState() && true == DataCommandSend.isCommandReplyOK(sendDataNode)) {
					// 当前消息为解绑定消息且同步成功

				} else {
					// 其他情况（数据库操作失败）
					sendDataNode = null;
				}

			} else {
				// 其他消息（非法消息）
				aLog.print(Log.debug, "收到非初始化消息（丢弃），消息ID：" + dataID + " NodeNum:" + aNodeNum);
			}

			if (sendDataNode != null) {
				// 发送回复消息，不需要对方回复
				if (false == sendDataNode(aNodeNum, IONodeType.UNINIT, sendDataNode, EReplyNeed.NOREPLY)) {
					aLog.print(Log.warn, "发送回复消息失败" + " NodeNum:" + aNodeNum);
					break;
				}
				sendDataNode = null;
			}

			// 发送初始化列表
			// // 注：若刷卡机强烈需要在此有一段间隔，可将此段放在判断是否有可处理消息之前（可推后一个主循环周期时间）
			if (EInitState.TO_SEND_INIT_LIST == uninitNode.getIONode(aNodeNum).getInitState()) {
				// 获取初始化列表
				Queue<DataSend> dataSendQueue = new LinkedList<DataSend>();
				this.getInitDataList(aDBAccess, aNodeNum, dataSendQueue);

				// 插入初始化完成命令
				sendDataNode = DataCommandSend.getDataCommandSendInitDown();
				if (sendDataNode != null) {
					dataSendQueue.offer(sendDataNode);
				}
				sendDataNode = null;

				// 发送初始化列表，全部都需要回复确认
				if (false == sendDataNodes(aNodeNum, IONodeType.UNINIT, dataSendQueue, EReplyNeed.NEEDREPLY)) {
					aLog.print(Log.warn, "发送消息失败" + " NodeNum:" + aNodeNum);
					break;
				}
				dataSendQueue.clear();
				dataSendQueue = null;

				// 初始化消息列表已发送，进入等待回复状态
				uninitNode.getIONode(aNodeNum).setInitState(EInitState.WAITING_REPLY);
			}

			recvDataNode = recvDataNode(aNodeNum, IONodeType.UNINIT); // 重新取接收池
		}

	}

	// 处理单个已初始化节点数据
	// ？？处理不能阻塞（处理一个节点到最长时间）// 先处理完当前已存在的数据
	// 每次处理一个IO节点：看是否线程已超时终止，读接收池，处理并发送回复消息，发送待同步消息
	void processOneInitedNode(final Log aLog, final Notification aNotification, final DBAccess aDBAccess, final int aNodeNum) {
		// 参数检查
		if (null == aDBAccess || true == isIONodeNull(aNodeNum, IONodeType.INITED)) {
			return;
		} else if (this.isIONodeThreadEnd(aNodeNum, IONodeType.INITED) == true) {
			aLog.print(Log.info, "已初始化节点线程已终止，删除节点" + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
			logFileManagerforIO.printToFile(this.getNodeLog(aNodeNum, IONodeType.INITED)); // IO节点日志写入文件

			// 删除节点前的善后工作
			// // 标记预约已同步，并通知预约失败
			this.processBookingListUpdateFail(aLog, aNotification, aDBAccess, aNodeNum, IONodeType.INITED);
			// 删除节点
			this.delNode(aLog, aNodeNum, IONodeType.INITED);
			return;
		}

		logFileManagerforIO.printToFile(getNodeLog(aNodeNum, IONodeType.INITED)); // 刷卡机节点日志写入文件

		// 处理节点数据
		int dataNodeNum = this.getRecvDataCount(aNodeNum, IONodeType.INITED); // 取当前接收池大小
		if (dataNodeNum > IONodeList.MAXNUM_NODEDATA) { // 单次处理消息条数超过上限
			aLog.print(Log.warn, "已初始化节点待处理消息过多，"  + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED) + "消息条数：" + dataNodeNum);
			dataNodeNum = IONodeList.MAXNUM_NODEDATA;
		}

		DataRecv recvDataNode = this.recvDataNode(aNodeNum, IONodeType.INITED); // 接收消息
		DataSend sendDataNode = null; // 接收消息触发到回复消息
		final DoingForDB newDoingForDB = this.getDoingForDB(this.getInstrumentID(aNodeNum, IONodeType.INITED), this.getInstrumentMacAddr(aNodeNum, IONodeType.INITED));
		while (recvDataNode != null && (dataNodeNum--) > 0) {
			// 处理一个数据
			// log.print("process", Log.debug,
			// "收到数据，ID：" + Integer.toString(recvDataNode.getDataID()));

			// 逻辑处理 // 已初始化状态
			int dataID = recvDataNode.getDataID();
			// Log.println("待处理消息dataID " + dataID + ":" +
			// recvDataNode.toString()); // debug
			if (DataHeartBeatRecv.dataID != dataID)
				Log.println("待处理消息dataID " + dataID + ":" + recvDataNode.toString() + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
			if (DataHeartBeatRecv.dataID == dataID) {
				// 收到心跳包
				// Log.println("主循环线程收到心跳包--已初始化");
				// 收到心跳包后除回复心跳包
				sendDataNode = DataHeartBeatSend.getInstance(((DataHeartBeatRecv) recvDataNode).getClientTimeStamp());
			} else if (DataCommandRecv.dataID == dataID) {
				// 收到通用消息（关注回复消息）
				// Log.println("主循环线程收到通用消息");
				DataCommandRecv dataRecvCommand = (DataCommandRecv) recvDataNode;
				if (true == dataRecvCommand.isReplyOK()) {
					// 收到回复OK消息，且对应消息不为空
					dataRecvCommand.getDataSendOfReply().setUpdated(aDBAccess);
				}
				sendDataNode = null;
			} else if (DataUserInTrainingList.dataID == dataID) {
				// 直接写数据库消息：培训记录
				sendDataNode = ((DataUserInTrainingList) recvDataNode).getReplyData(aDBAccess, newDoingForDB);
			} else if (DataSystemEventList.dataID == dataID) {
				// 系统事件列表
				sendDataNode = ((DataSystemEventList) recvDataNode) // ？？条件不安全
						.getReplyData(aDBAccess, newDoingForDB);
			} else if (DataUserEventList.dataID == dataID) {
				// 用户事件列表
				sendDataNode = ((DataUserEventList) recvDataNode).getReplyData(aDBAccess, newDoingForDB);
			} else if (DataClientSessionList.dataID == dataID) {
				// 使用记录
				sendDataNode = ((DataClientSessionList) recvDataNode).getReplyData(aDBAccess, newDoingForDB);
			} else if (DataInstrumentBinding.dataID == dataID) {
				// 收到绑定信息（可能为绑定或解绑定）
				final DataInstrumentBinding dataInstrumentBinding = (DataInstrumentBinding) recvDataNode;
				// 区分绑定与解绑定
				if (DataInstrumentBinding.EBindingState.Release == dataInstrumentBinding.getBindingState()) {
					// 是解绑定而非绑定
					Log.println("主循环线程收到：InstrumentBinding-unbind" + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
					sendDataNode = ((DataInstrumentBinding) recvDataNode).getReplyData(aDBAccess);
					if (sendDataNode != null) { // 回复消息
						if (sendDataNode(aNodeNum, IONodeType.INITED, sendDataNode, EReplyNeed.NOREPLY) == false) { // 不需要回复
							aLog.print(Log.warn, "发送回复消息失败" + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
						}
					}
					// this.endIONodeThread(aNodeNum, IONodeType.INITED);
					// // 解绑定后若立即关闭连接可能消息没发过去，可由刷卡机主动关闭连接，通信服务被动关闭连接（等超时）
					sendDataNode = null;
					break;
				} else {
					// 收到绑定消息
					Log.println("主循环线程收到：InstrumentBinding-bind" + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
				}

			} else {
				// 非法消息
				sendDataNode = null;
			}

			if (sendDataNode != null) { // 回复消息
				if (sendDataNode(aNodeNum, IONodeType.INITED, sendDataNode, EReplyNeed.NOREPLY) == false) { // 不需要回复
					aLog.print(Log.warn, "发送回复消息失败" + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
					break;
				}
			}
			sendDataNode = null;
			recvDataNode = this.recvDataNode(aNodeNum, IONodeType.INITED); // 重新取接收池
		}

		// 主动推送的待同步消息
		// // 若符合更新条件则执行更新（刷卡机网络状态好，一个一个更新）
		// // 可能需要按顺序来（若需按顺序来可封装小函数，逐个调即可，可类似初始化顺序一样用数组实现）
		if (true == this.getUpdateStateOfNodeNet(aNodeNum)) {					
			// System.out.println("刷卡及网络状态好，主动推送消息 --processOneInitedNode");
//			Log.println("刷卡及网络状态好，主动推送消息 --processOneInitedNode");
			for (EUpdateType updateType : EUpdateType.values()) {
				if (true == this.getUpdateNeed(aNodeNum, updateType)) {
					// 该类型待更新
					Log.println("执行同步，待同步类型：" + updateType + "，仪器ID：" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
					this.doUpdateForInitNode(aLog, aDBAccess, aNodeNum, updateType);
				} else {
					// 该类型不需要更新
				}
			}
		}

	}

	// 获取初始化列表（未初始化节点）
	// // 列表顺序要求可变化
	private void getInitDataList(final DBAccess aDBAccess, final int aNodeNum, Queue<DataSend> aDataSendQueue) {
		final boolean forInit = true;
		final int instrumentID = this.getInstrumentID(aNodeNum, IONodeType.UNINIT);
		// 枚举类型在数组中到顺序即为初始化列表发送顺序
		EUpdateType[] updateTypesArray = { EUpdateType.InstrumentDetail, EUpdateType.ReservationList, EUpdateType.GlobalConfig, EUpdateType.CalendarList, EUpdateType.TempCardBindingList, EUpdateType.UserRightList, EUpdateType.UserList, EUpdateType.AccountList, EUpdateType.BookingList };
		Queue<DataSend> dataSendQueueTemp = null;
		for (EUpdateType updateType : updateTypesArray) {
			// 按照数组中顺序取初始化列表
			dataSendQueueTemp = updateType.getIOUpdateData(aDBAccess, instrumentID, forInit);
			if (dataSendQueueTemp != null) {
				while (false == dataSendQueueTemp.isEmpty()) {
					aDataSendQueue.offer(dataSendQueueTemp.poll());
				}
			}
		}
		// 插入初始化附加列表（本仪器最近一条用户事件记录，系统事件记录，管理员使用记录，普通用户使用记录）
		// // 最近一条用户事件记录
		dataSendQueueTemp = DataUserEventListLast.getInstance(aDBAccess, instrumentID);
		if (dataSendQueueTemp != null) {
			while (false == dataSendQueueTemp.isEmpty()) {
				aDataSendQueue.offer(dataSendQueueTemp.poll());
			}
		}
		// 最近一条系统事件记录
		dataSendQueueTemp = DataSystemEventListLast.getInstance(aDBAccess, instrumentID);
		if (dataSendQueueTemp != null) {
			while (false == dataSendQueueTemp.isEmpty()) {
				aDataSendQueue.offer(dataSendQueueTemp.poll());
			}
		}
		// 最近两条使用记录（管理员和普通用户）
		dataSendQueueTemp = DataClientSessionListLast.getInstance(aDBAccess, instrumentID);
		if (dataSendQueueTemp != null) {
			while (false == dataSendQueueTemp.isEmpty()) {
				aDataSendQueue.offer(dataSendQueueTemp.poll());
			}
		}
	}

	// 处理已死IO节点同步失败的预约列表
	// // 可能主要针对已初始化节点
	private void processBookingListUpdateFail(Log aLog, final Notification aNotification, final DBAccess aDBAccess, final int aNodeNum, IONodeType aIONodeType) {
		// // 预约失败标记已同步，并通知预约失败
		NoteList newNoteList = new NoteList();
		
		// 查看是否有已标记待同步但由于网络状态不好等原因未查询数据库并插入节点发送缓存队列的预约消息
		// // 仅针对已初始化节点
		if (true == this.getUpdateNeed(aNodeNum, EUpdateType.BookingList)) {
			Log.println("IO线程终止后，预约待同步标记未清空，从数据库取出待同步预约列表，以标记预约同步失败");
			// 该类型待更新
			this.doUpdateForInitNode(aLog, aDBAccess, aNodeNum, EUpdateType.BookingList);
		}
		
		int num = this.getDataSendNeedReplyCount(aNodeNum, aIONodeType);		
		//Log.println("1num:" + num);		
		DataSend dataNode = this.getDataSendNeedReply(aNodeNum, aIONodeType);
		while (null != dataNode && (num--) > 0) {
			if (DataBookingList.dataID == dataNode.getDataID()) {
				// 标记同步失败（部分需要再次同步，部分不需要再次同步），并准备待通知预约ID列表
				((DataBookingList) dataNode).setUpdatedFail(aDBAccess, newNoteList);
			}
			dataNode = this.getDataSendNeedReply(aNodeNum, aIONodeType); // 取下一条未发送的待回复消息
		}		
		//Log.println("2num:" + num);		
		if (newNoteList.size() > 0) {
			newNoteList.sendAll(); // 发布通知（异步调用，立即返回）

		} else {
			// Log.println("没有同步失败的预约");
		}

	}

	// 处理监视服务消息
	void processWitnessData(ServerForWitness aServerForWitness) {
		// 参数检查
		if (null == aServerForWitness) {
			return;
		}
		// 处理
		if (true != aServerForWitness.getProcessStatus()) {
			return;
		} else {
			final DataForWitness.ECommand newCommand = DataForWitness.ECommand.getCommand(aServerForWitness.getReq());
			switch (newCommand) {
			case TimeSeconds:
				aServerForWitness.setAck((int) (System.currentTimeMillis() / 1000));
				// System.out.println(System.currentTimeMillis() / 1000);
				break;
			default:
				break;
			}
			return;
		}
	}

	// 读取IO接收池大小（出错返回-1）
	private int getRecvDataCount(int aNodeNum, IONodeType aIsInited) {
		if (aIsInited == IONodeType.UNINIT) {
			return uninitNode.getIONode(aNodeNum).getRecvDataCount();
		} else {
			return initedNode.getIONode(aNodeNum).getRecvDataCount();
		}
	}

	// 从某IO接收一条消息，参数：IO序号；返回值：数据类的基类
	private DataRecv recvDataNode(int aNodeNum, IONodeType aIsInited) {
		if (aIsInited == IONodeType.UNINIT) {
			return uninitNode.getIONode(aNodeNum).getRecvData();
		} else {
			return initedNode.getIONode(aNodeNum).getRecvData();
		}
	}

	// 向某IO发一条消息，参数：IO所维护仪器ID，数据发送类的基类，是否等待回复
	private boolean sendDataNode(final int aNodeNum, final IONodeType aIsInited, final DataSend aDataNode, final EReplyNeed aIsNeedReply) {
		//Brown TODO 
		if (aIsInited == IONodeType.UNINIT) {
			return uninitNode.getIONode(aNodeNum).addData(aDataNode, aIsNeedReply);
		} else {
			return initedNode.getIONode(aNodeNum).addData(aDataNode, aIsNeedReply);
		}
	}

	// 向某IO发一组消息，参数：IO所维护仪器ID，数据发送类的基类，是否等待回复,返回发送结果
	private boolean sendDataNodes(final int aNodeNum, final IONodeType aIsInited, final Queue<DataSend> aDataSendQueue, final EReplyNeed aIsNeedReply) {
		while (aDataSendQueue.size() > 0) {
			if (false == this.sendDataNode(aNodeNum, aIsInited, aDataSendQueue.poll(), aIsNeedReply)) {
				return false;
			}
		}
		return true;
	}

	// 读取IO待回复发送池大小（出错返回-1）
	private int getDataSendNeedReplyCount(int aNodeNum, IONodeType aIsInited) {
		if (aIsInited == IONodeType.UNINIT) {
			return uninitNode.getIONode(aNodeNum).getDataSendNeedReplyCount();
		} else {
			return initedNode.getIONode(aNodeNum).getDataSendNeedReplyCount();
		}
	}

	// 从某IO读取一条待回复消息，参数：IO序号；返回值：数据类的基类
	private DataSend getDataSendNeedReply(int aNodeNum, IONodeType aIsInited) {
		if (aIsInited == IONodeType.UNINIT) {
			return uninitNode.getIONode(aNodeNum).getDataSendNeedReply();
		} else {
			return initedNode.getIONode(aNodeNum).getDataSendNeedReply();
		}
	}

	// 获取某个节点日志缓存
	private Log getNodeLog(int aNodeNum, IONodeType aIsInited) {
		IONode ioNode = null;
		if (IONodeType.UNINIT == aIsInited) {
			ioNode = uninitNode.getIONode(aNodeNum);
		} else {
			ioNode = initedNode.getIONode(aNodeNum);
		}
		if (null != ioNode) {
			return ioNode.getLog();
		} else {
			return null;
		}
	}

	// 获取某IO节点网络状态（更新相关）
	private boolean getUpdateStateOfNodeNet(int aNodeNum) {
		return this.initedNode.getIONode(aNodeNum).getNetStateForUpdate();
	}

	// 查看节点是否有更新需求
	private boolean getUpdateNeed(int aNodeNum, EUpdateType aUpdateType) {
		return this.initedNode.getIONode(aNodeNum).getUpdateNeed(aUpdateType);
	}

	// 设置更新请求（数据库接口封装类或web接口封装类调用）
	// // 设置全部节点更新
	private void setUpdateReqForAllInitedNode(EUpdateType aUpdateType, long aUpdateTime) {
		// 群发待同步设置
		for (int i = 0; i < this.initedNode.getSize(); i++) {
			if (false == IONodeList.this.isIONodeNull(i, IONodeType.INITED)) {
				this.setUpdateReqByNodeNum(i, aUpdateType, aUpdateTime);
			}
		}
	}

	// 设置某个节点更新
	private void setUpdateReqByInstrumentID(int aInstrumentID, EUpdateType aUpdateType, long aUpdateTime) {
		this.setUpdateReqByNodeNum(IONode.instrumentIDToNodeNum(aInstrumentID), aUpdateType, aUpdateTime);
	}

	// 设置更新需求（全数据库扫描或web通知时调此函数）
	private void setUpdateReqByNodeNum(int aNodeNum, EUpdateType aUpdateType, long aUpdateTime) {
		this.initedNode.getIONode(aNodeNum).setUpdateReq(aUpdateType, aUpdateTime);
	}

	// 设置更新已完成（主循环线程调过更新函数后调此函数修改更新时间）
	private void setUpdateDoneByNodeNum(int aNodeNum, EUpdateType aUpdateType, long aUpdateTime) {
		this.initedNode.getIONode(aNodeNum).setUpdateDone(aUpdateType, aUpdateTime);
	}

	// 执行同步-对某已初始化节点某类待同步消息（主动推送时调用）
	// // 仅针对已初始化节点，未初始化节点无此需求
	// // 读取数据库，发送结果
	// //
	// 注：先标记同步完成，再发给IO线程，若发给IO线程失败，则重新标记待同步（完整发给IO线程即认为同步完成，若收到刷卡机回复则修改数据库同步标志，
	// // // 若没有收到回复或刷卡机突然离线等会在刷卡机再次上线时触发主动查询同步）
	private void doUpdateForInitNode(final Log aLog, final DBAccess aDBAccess, final int aNodeNum, EUpdateType aUpdateType) {
		Queue<DataSend> dataSendQueue = null;
		 Log.println("刷卡机" + aNodeNum + "需更新类型" + aUpdateType.toString()
		 + "-processOneInitedNode");
		// 置IO节点更新需求状态
		this.setUpdateDoneByNodeNum(aNodeNum, aUpdateType, System.currentTimeMillis());
		// 访问数据库读取待同步列表
		final boolean isForInit = false;
		dataSendQueue = aUpdateType.getIOUpdateData(aDBAccess, this.getInstrumentID(aNodeNum, IONodeType.INITED), isForInit);
		if (null == dataSendQueue) {
			Log.println("待同步列表为null，待同步类型：" + aUpdateType.toString() + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
			// 刷卡机待更新，从数据库里读到到消息条数为零
			// 可能更新状态标记错了，或数据库down掉了查不出来（小概率时间，其他地方会处理保证同步完备性），可直接清空待更新标志
			return;
		} else {
			// 发送同步消息
			// Log.println("待同步列表长度：" + dataSendQueue.size() + "，待同步类型：" + aUpdateType.toString() + " InstrumentID:" + this.getInstrumentID(aNodeNum, IONodeType.INITED));
			if (false == sendDataNodes(aNodeNum, IONodeType.INITED, dataSendQueue, EReplyNeed.NEEDREPLY)) { // 需要回复
				// 发送消息失败一般是刷卡机异常（若刷卡机短时异常需要再次同步，若刷卡机长时间异常
				// 肯定会丢掉心跳包，导致推迟该刷卡机的更新）
				aLog.print(Log.warn, "发送回复消息失败");
				this.setUpdateReqByNodeNum(aNodeNum, aUpdateType, System.currentTimeMillis());
				return;
			}

		}

	}

	// 获取已出现的最大节点序号
	int getMaxInitedNodeIndex() {
		return this.maxNodeNum;
	}

	// 插入节点-将节点由未初始化队列移除放在已初始化队列合适到位置，主循环线程中调用
	private boolean addInitedNode(final Log aLog, final int aUninitNodeNum, final int aInstrumentID) {
		final int initedNodeNum = IONode.instrumentIDToNodeNum(aInstrumentID); // 仪器ID转为已初始化节点序号
		if (true == this.isConnected(initedNodeNum)) {
			Log.println("目标节点非空，删除原目标节点");
			delNode(aLog, initedNodeNum, IONodeType.INITED);
		}
		uninitNode.getIONode(aUninitNodeNum).setInitState(EInitState.INITED);// 标记初始化状态为已初始化
		// 插入节点
		
		initedNode.setIONode(initedNodeNum, uninitNode.getIONode(aUninitNodeNum));
		uninitNode.clearIONode(aUninitNodeNum);
		if (initedNodeNum > this.maxNodeNum)
			this.maxNodeNum = initedNodeNum; 
		Log.println("转移节点到已初始化队列，当前节点序号:" + initedNodeNum + "最大节点序号:" + this.maxNodeNum);
		return true;

	}

	// 获取已初始化节点仪器ID
	private int getInstrumentID(final int aNodeNum, final IONodeType isInited) {
		if (IONodeType.UNINIT == isInited) {
			return this.uninitNode.getIONode(aNodeNum).getInstrumentID();
		} else {
			return this.initedNode.getIONode(aNodeNum).getInstrumentID();
		}
	}

	// 获取已初始化节点仪器Mac地址
	private String getInstrumentMacAddr(final int aNodeNum, final IONodeType isInited) {
		if (IONodeType.UNINIT == isInited) {
			return this.uninitNode.getIONode(aNodeNum).getMacAddr();
		} else {
			return this.initedNode.getIONode(aNodeNum).getMacAddr();
		}
	}

	// 判断某节点是否已连接刷卡机
	private boolean isConnected(final int aNodeNum) {
		if (null == initedNode.getIONode(aNodeNum)) {
			return false;
		} else {
			return true;
		}
	}

	// 软关闭IO节点线程，不检查参数有效性
	private void endIONodeThread(final int aNodeNum, final IONodeType isInited) {
		if (isInited == IONodeType.UNINIT) {
			uninitNode.getIONode(aNodeNum).end();
		} else {
			initedNode.getIONode(aNodeNum).end();
		}
	}

	// 删除所有节点
	void delAllNode(final Log aLog) {
		Log.println("delAllNode()");
		for (int i = 0; i < this.MAXNUM_IONODE; i++) {
			// 待初始化节点
			if (false == this.isIONodeNull(i, IONodeType.UNINIT)) {
				this.delNode(aLog, i, IONodeType.UNINIT);
			}
		}
		for (int i = 0; i < this.MAXNUM_IONODE; i++) {
			// 已初始化节点
			if (false == this.isIONodeNull(i, IONodeType.INITED)) {
				this.delNode(aLog, i, IONodeType.INITED);
			}
		}
	}

	// 按序号删除一个节点，由主循环线程在IO线程超时或其他需要删除IO节点（如有重复连接）时调用
	// // 带超时强制关闭
	private void delNode(final Log aLog, final int aNodeNum, final IONodeType isInited) {
		if (IONodeType.UNINIT == isInited) {
			if (uninitNode.getIONode(aNodeNum).isAlive()) {
				long timeOutTimerEndTime = System.currentTimeMillis() + TimeoutTimerMaxTime;
				uninitNode.getIONode(aNodeNum).end();
				while (uninitNode.getIONode(aNodeNum).isAlive()) {
					if (System.currentTimeMillis() > timeOutTimerEndTime) {
						aLog.print(Log.warn, "关闭节点线程超时，强制关闭");
						uninitNode.getIONode(aNodeNum).closeSocket(); // 强制关闭（通过关闭socket）
						break;
					}
				}
			}
			uninitNode.clearIONode(aNodeNum);
			aLog.print(Log.info, "已删除未初始化节点，节点序号:" + aNodeNum);
		} else {
			if (initedNode.getIONode(aNodeNum).isAlive()) {
				long timeOutTimerEndTime = System.currentTimeMillis() + TimeoutTimerMaxTime;
				initedNode.getIONode(aNodeNum).end();
				while (initedNode.getIONode(aNodeNum).isAlive()) {
					if (System.currentTimeMillis() > timeOutTimerEndTime) {
						aLog.print(Log.info, "关闭节点线程超时，强制关闭");
						initedNode.getIONode(aNodeNum).closeSocket();// 强制关闭（通过关闭socket）
						break;
					}
				}
			}
			initedNode.clearIONode(aNodeNum);
			aLog.print(Log.info, "已删除已初始化节点，节点序号:" + aNodeNum);
		}
	}

	// 判断IO节点是否为空
	private boolean isIONodeNull(final int aNodeNum, final IONodeType isInited) {
		if (isInited == IONodeType.UNINIT) {
			if (uninitNode.getIONode(aNodeNum) == null) {
				return true;
			} else {
				return false;
			}
		} else if (isInited == IONodeType.INITED) {
			if (initedNode.getIONode(aNodeNum) == null) {
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}

	// 检查IO节点线程是否已终止
	private boolean isIONodeThreadEnd(final int aNodeNum, final IONodeType isInited) {
		if (IONodeType.UNINIT == isInited) {
			return uninitNode.getIONode(aNodeNum).isEnd();
		} else {
			return initedNode.getIONode(aNodeNum).isEnd();
		}
	}

	// 枚举（IO节点类型：是否已初始化）
	// // 代码基于本枚举仅有两个值
	enum IONodeType {
		UNINIT, INITED
	}

	DoingForListenThread getDoingForListenThread() {
		return new DoingForListenThread();
	}

	// IO节点索引的监听线程封装类
	class DoingForListenThread {

		private DoingForListenThread() {
		}

		// 添加节点-添加新节点到未初始化队列
		boolean addUninitNode(IONode newNode) {
			// log.print("add", Log.info, "addonenode");
			for (int i = 0; i < IONodeList.this.uninitNode.getSize(); i++) {
				if (IONodeList.this.uninitNode.getIONode(i) == null) {
					IONodeList.this.uninitNode.setIONode(i, newNode);
					return true;
				}
			}
			return false; // 未初始化队列满
		}
	}

	DoingForWeb getDoingForWeb() {
		return new DoingForWeb();
	}

	// IO节点索引的web封装类
	// // 供webservice调用的方法封装
	public class DoingForWeb {

		private DoingForWeb() {
		}

		// 查询所有刷卡机状态
		// // 返回数组中值对应刷卡机状态0为离线，其他情况为最后一次收到的该刷卡机心跳包中的状态
		// // 返回节点数可能小于所有刷卡机个数，小于的部分全部为不在线，最后一个节点不一定为在线
		int[] getInstrumentStateList() {
			// Log.println("web获取刷卡机状态");
			final int instrumentCount = IONodeList.this.maxNodeNum + 1;
			int[] deviceList = new int[instrumentCount];
			for (int i = 0; i < deviceList.length; i++) {
				if (true == this.getIONetState(i)) {
					// System.out.print("非离线：" + deviceList[i] + ":");
					deviceList[i] = this.getLastStateOfNode(i); // 在线
				} else {
					// System.out.print("离线：" + deviceList[i] + ":");
					deviceList[i] = 0; // 离线
				}
			}
			// Log.println("刷卡机状态数组：" + deviceList.toString());
			return deviceList;
		}

		// 获取刷卡机最后一个心跳包中状态（仅针对已初始化仪器）
		private int getLastStateOfNode(final int aNodeNum) {
			return IONodeList.this.initedNode.getIONode(aNodeNum).getLastState();
		}

		// 获取刷卡机网络状态（web查询用，仅针对已初始化仪器）
		private boolean getIONetState(final int aNodeNum) {
			if (false == IONodeList.this.isIONodeNull(aNodeNum, IONodeType.INITED)) {
				if (true == IONodeList.this.initedNode.getIONode(aNodeNum).getNetStateForWeb()) {
					return true;
				}
			}
			return false;
		}

		// 写入同步需求到各个刷卡机节点
		// // 预约失败与同步失败，模拟同步失败，在db接口中处理预约失败（同步失败包括预约失败）
		void setNewUpdateReq(final EUpdateType aEUpdateType, final int aID1, final int aID2) {
			// 若已请求更新则覆盖
			// UserList, AccountList, UserRightList, TempCardBindingList,
			// InstrumentDetail, ReservationList, BookingList;
			Log.println("web通知待同步，类型：" + aEUpdateType.toString() + " ID1:" + aID1 + " ID2:" + aID2);
			final long updateTime = System.currentTimeMillis();
			switch (aEUpdateType) {
			case UserList:
			case AccountList:
			case CalendarList:
			case GlobalConfig: {
				// 与仪器无关消息，群发待同步
				IONodeList.this.setUpdateReqForAllInitedNode(aEUpdateType, updateTime);
				break;
			}
			case UserRightList:
			case InstrumentDetail:
			case ReservationList:
			case BookingList:
			case TempCardBindingList: {
				// 与仪器有关，单发待同步
				final int instrumentID = aID1;// 仪器ID
				final int nodeNum = IONode.instrumentIDToNodeNum(instrumentID); // 获取仪器ID对应数组序号
				if (nodeNum < 0) {
					// 仪器ID非法
					Log.println("仪器ID非法：" + instrumentID);
					break;
				}
				if (true == this.getIONetState(nodeNum)) {
					// 若仪器在线，标记待同步
					IONodeList.this.setUpdateReqByInstrumentID(instrumentID, aEUpdateType, updateTime);
				} else {
					// 若仪器离线，特殊处理
					if (EUpdateType.BookingList == aEUpdateType) {
						// 标记预约同步失败，必要时发布通知
						Log.println("仪器离线标记预约同步失败，仪器ID：" + instrumentID);
						DBAccess iDBAccess = new DBAccess(new Log()); // 数据库接口
						if (true == iDBAccess.openDbConnection()) {
							// 获取待同步消息列表
							Queue<DataSend> newDataBookingList = DataBookingList.getInstance(iDBAccess, instrumentID, false);
							if (null != newDataBookingList) {
								// 标记待同步消息列表同步失败
								NoteList newNoteList = new NoteList();
								while (null != newDataBookingList.peek()) {
									DataSend dataNode = newDataBookingList.poll();
									// 标记同步失败，并准备待通知预约列表，（部分需要再次同步，部分不需要再次同步）
									((DataBookingList) dataNode).setUpdatedFail(iDBAccess, newNoteList);
									dataNode = null;
								}
								newNoteList.sendAll(); // 发布通知（异步调用，立即返回）
								// newNoteList.clear(); //
								// 不能立即clear，立即clear为异步操作可能此时邮件列表还没有发完
								// newNoteList = null;
							}
							iDBAccess.closeDbConnection();
						}
						iDBAccess = null;
					} else {
						// 其他类型待同步通知
						// // 直接放弃标记待同步
					}
				}
				break;
			}
			default:
				break;
			}
			Log.println("web通知返回");
		}
	}

	DoingForDB getDoingForDB(int aInstrumentID, String aMacAddr) {
		return new DoingForDB(aInstrumentID, aMacAddr);
	}

	// 数据库操作封装类（与仪器有关）
	class DoingForDB {
		private int instrumentID;
		private String macAddr;

		private DoingForDB(int aInstrumentID, String aMacAddr) {
			this.instrumentID = aInstrumentID;
			this.macAddr = aMacAddr;
		}

		int getInstrumentID() {
			return this.instrumentID;
		}

		String getMacAddr() {
			return this.macAddr;
		}

		// ??此函数与web的函数差不多，是否可合并
		// 仅涉及用户，账户，权限，可灵活删减（限定设置同步权限）
		void setNewUpdateReq(EUpdateType aEUpdateType) {
			final long updateTime = System.currentTimeMillis();
			switch (aEUpdateType) {
			case UserList:
			case AccountList:

			// case CalendarList:
			{
				// 群发待同步
				IONodeList.this.setUpdateReqForAllInitedNode(aEUpdateType, updateTime);
				break;
			}
			case UserRightList:
			// case InstrumentDetail:
			// case ReservationList:
			// case BookingList:
			// case TempCardBindingList:
			// case GlobalConfig:
			{
				// 单发待同步
				if (false == IONodeList.this.isIONodeNull(IONode.instrumentIDToNodeNum(this.instrumentID), IONodeType.INITED)) {
					IONodeList.this.setUpdateReqByInstrumentID(this.instrumentID, aEUpdateType, updateTime);
				}
				break;
			}
			default:
				break;
			}
		}
	}

	// 刷卡机待同步需求列表类（数据库接口用，遍历数据库待同步列表用）
	// // 与仪器无关，设置待更新需求列表
	class IOUpdateNeedList {
		private long timeOfUpdateReq = 0; // 设置待更新的时间

		private IOUpdateNeedList() {
		}

		void setTime() {
			this.timeOfUpdateReq = System.currentTimeMillis();
		}

		// 设置某刷卡机某类型待更新
		void setUpdateNeed(int aInstrumentID, EUpdateType aEUpdateType) {
			final int nodeNum = IONode.instrumentIDToNodeNum(aInstrumentID);
			if (false == IONodeList.this.isIONodeNull(nodeNum, IONodeType.INITED)) {
				IONodeList.this.setUpdateReqByInstrumentID(aInstrumentID, aEUpdateType, timeOfUpdateReq);
			}
		}
	}

	// 线程安全大小可变到数组封装-供IO节点数组使用
	// // 构造函数有一个初始大小。对象可以增大大小，获得当前大小，节点初始值为null
	// // 可用set赋值，get获得节点，clear将节点清空为null
	// // reset将数组长度变为0
	class IONodesVector {
		Vector<IONode> vector;

		// 可选结构
		// ArrayList<E>，可随机读写，插入删除节点，自动增长，手动改变大小（非线程安全）
		// Vector，线程安全
		// CopyOnWriteArrayList 线程安全

		IONodesVector(int aSize) {
			this.vector = new Vector<IONode>(aSize, aSize);
			for (int i = 0; i < aSize; i++) {
				this.vector.add(null);
			}
		}

		int getSize() {
			return this.vector.size();
		}

		void addSize(int aFullSize) {
			for (int i = this.getSize(); i < aFullSize; i++) {
				this.vector.add(null);
			}
		}

		void reset() {
			this.vector.clear();
		}

		IONode getIONode(int aIndex) {
			if (aIndex >= 0 && aIndex < this.getSize()) {
				return vector.get(aIndex);
			} else {
				return null;
			}

		}

		boolean setIONode(int aIndex, IONode aIONode) {
			if (aIndex >= 0 && aIndex < this.getSize()) {
				this.vector.set(aIndex, aIONode);
				return true;
			} else {
				return false;
			}
		}

		boolean clearIONode(int aIndex) {
			return this.setIONode(aIndex, null);
		}

	}
	
	// 结点状态列表（用于判断结点离线时间是否过长）
	// 封装性不太好？？
	class NodeStateVector {
		Vector<NodeState> vector;
		
		NodeStateVector(int aSize){
			this.vector = new Vector<NodeState>(aSize, aSize);
			for (int i = 0; i < aSize; i++) {
				this.vector.add(new NodeState());
			}
		}
	}
	
	private class NodeState {
		int state;
		long timeMills;
		boolean needNotice;
		boolean isNoticeOfflineFault;
		
		public NodeState(){
			state = 0;
			timeMills = 0;
			needNotice = false;
			isNoticeOfflineFault = false;
		}
	}

}

// ** 待改进

// ?? 加入webservice线程状态监视？？
// webservice线程异常处理？？(可能有安全隐患)
// // webserver到错误日志与结束检测？？
// // 暂时没有找到解决方案


// ？？检查null值
// 枚举对象也可能为null，具体值

// 调试不方便，到底消息收到了没有？？不好找
// 接受到哪些消息，发出了哪些消息
// // 刷卡机（双向），数据库（双向），web（单向）

// ** 说明

// 关于预约同步失败后由通信服务标记预约失败（不全为立即标记）
// // 若web通知预约待同步时IO线程已终止（刷卡机确认离线），通信服务立即标记预约失败
// // 若web通知预约待同步时IO线程认为网络状态不好，之后因刷卡机上时间无响应而关闭连接，通信服务会延迟标记预约失败，
// //即确认刷卡机离线后标记预约失败（时间可能为数个心跳包时间，由心跳包间隔时间MaxTimeOfHeartBeat和离线判断条件MaxNoPackageTimeForNet决定）

// MainLoop线程退出时，其他线程可能没有关闭，监视服务WitnessServer进程请求得不到有效回复会重启通信服务进程
// // 若需要MainLoop退出时整个通信服务进程退出，可调一个延时杀死通信服务的shell脚本

// 单次单个刷卡机节点的接收消息需要尽量处理完，否则可能造成恶性循环
// // 暂时一次最多处理一定数量消息，若消息过多则不再接收新消息还是清除当前所有消息（暂时采用前者，具体由IO线程控制）

// 有时会出现再次同步已同步成功的消息
// // 为了数据安全保证同步完全性，照成的结果是冗余发送（同一条更新重复同步），不会有严重后果
// //
// 同一类消息在短时间内有两次同步通知时（有可能是两次web通知或手动标记待同步需求），前一次更新还在队列缓存中或还没来得及标记已同步，会再次发送（概率小）
// // 通信服务Log中显示同一条预约很长时间后重新同步也可能失预约变更引起重新同步（正常情况）
// // （路）如果刷卡机返回通路进行的太快，由于mysql时间分辨率太低（1S）（相当没有同步成功，若网速太好且刷卡机很少可能经常出现，但不会照成严重问题）
// // （麦）数据库（存储过程）时间与php时间好像有几分钟的差别，可能标记已同步貌似成功了，但写进去的时间无效

// 心跳包由刷卡机主动发给通信服务，通信服务收到后向刷卡机回复心跳包

// 标记待更新：写入新记录（若刷卡机不在线则不置更新），数据库遍历，web通知（后两个涉及刷卡机不在线时立即反应，如bookinglist）
// 标记待同步时若刷卡机不在线需立即处理
// 几种数据库标记待更新可能可以合并（？？）（web标记待更新，数据库标记待更新，数据库全库扫描标记待更新）

// 数据库连接有问题时建立连接会卡几分钟后抛出异常，可不予考虑

// 检查各个服务状态并在需要时终止通信服务
// // 若监听线程（两个监听线程），数据库连接有问题则终止通信服务

// WebService通知处理（刷卡机更新）
// // 根据web通知修改个IO节点更新需求
// // 由webservice直接修改
// // 安全问题很小，即使出错了，结果仅是多查了一次数据库（可能查到的结果为0条待更新）

// 某个IO跑疯掉时，mainloop应能够关闭线程
// // 如果需要检测IO是否已死在某个循环，可用一个时间状态有IO定期刷新，mainloop来检查

// 刷卡机节点管理：
// // 刷卡机节点做成定长（正常情况下不增加长度，可以处理动态改变，但仅保证定长范围内不出问题？？）
// 刷卡机节点动态数组改进：？？
// 要求线程安全
// // 要求：可以随机修改某节点，大小可变（可增长）
// // 任意时候可能add一个节点，若节点够大需要马上增上数组（数组不会逐个增大，会突然增大）
// 线程安全：并发修改表结构？？并发修改节点内容
// 结构修改是指添加或删除节点，或显示调整底层数组大小
// // 解决
// // 对节点数组到结构变化仅有两个地方，监听线程添加待初始化节点，主循环线程添加初始化节点
// // 主循环线程会修改节点
// // 其他情况对节点数组的操作均为读操作
// 关于节点管理的做法（可能效率高，但协调难度很大，暂保留原来到方案）
// IO节点初始化时不为空，而是离线节点，可以写AB？？

// 注：初始化完成后正常通信，服务端有可能发部分过期数据（到时侯不要太惊讶）

// 注：关于检查同步频率：主循环新线程每过（例如半小时）很长时间执行一次全数据库检查，
// 将查出的需更新的type写入IO节点（按时间格式，后来的更新前来的）
// 主循环线程执行某个已初始化IO节点时，若网络稳定（如连续收到十个心跳包，且有需同步
// 更新）则调用数据库接口函数按type取需同步读消息，并修改IO节点的更新状态列表

